/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "verify_ani_gtest.h"

namespace ark::ets::ani::verify::testing {

class ReferenceEqualsTest : public VerifyAniTest {
public:
    void SetUp() override
    {
        VerifyAniTest::SetUp();

        ASSERT_EQ(env_->GetNull(&nullRef), ANI_OK);

        ASSERT_EQ(env_->String_NewUTF8("abc", 3U, &str), ANI_OK);

        ASSERT_EQ(env_->FindModule("verify_reference_equals_test", &module), ANI_OK);
        std::array functions = {
            ani_native_function {"stackRefWithLocal",
                                 "X{C{std.core.Null}C{std.core.String}}:", reinterpret_cast<void *>(StackRefWithLocal)},
            ani_native_function {"stackRefWithGlobal", "X{C{std.core.Null}C{std.core.String}}:",
                                 reinterpret_cast<void *>(StackRefWithGlobal)},
        };
        ASSERT_EQ(env_->Module_BindNativeFunctions(module, functions.data(), functions.size()), ANI_OK);
    }

protected:
    ani_module module {};
    ani_ref nullRef {};
    ani_string str {};

private:
    static void StackRefWithLocal(ani_env *env, ani_ref ref)
    {
        ani_boolean res {};

        ani_ref nullRef {};
        ASSERT_EQ(env->GetNull(&nullRef), ANI_OK);

        ASSERT_EQ(env->c_api->Reference_Equals(env, ref, nullRef, &res), ANI_OK);
        ASSERT_EQ(res, ANI_TRUE);
    }

    static void StackRefWithGlobal(ani_env *env, ani_ref ref)
    {
        ani_boolean res {};

        ani_ref nullRef {};
        ASSERT_EQ(env->GetNull(&nullRef), ANI_OK);

        ani_ref gref {};
        ASSERT_EQ(env->GlobalReference_Create(nullRef, &gref), ANI_OK);
        ASSERT_EQ(env->c_api->Reference_Equals(env, ref, gref, &res), ANI_OK);
        ASSERT_EQ(env->GlobalReference_Delete(gref), ANI_OK);

        ASSERT_EQ(res, ANI_TRUE);
    }
};

TEST_F(ReferenceEqualsTest, wrong_env)
{
    ani_boolean res = ANI_FALSE;

    ASSERT_EQ(env_->c_api->Reference_Equals(nullptr, nullRef, nullRef, &res), ANI_ERROR);
    std::vector<TestLineInfo> testLines {
        {"env", "ani_env *", "called from incorrect the native scope"},
        {"ref0", "ani_ref"},
        {"ref1", "ani_ref"},
        {"result", "ani_boolean *"},
    };
    ASSERT_ERROR_ANI_ARGS_MSG("Reference_Equals", testLines);
}

TEST_F(ReferenceEqualsTest, wrong_ref0)
{
    ani_boolean res = ANI_FALSE;
    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullptr, nullRef, &res), ANI_ERROR);
    std::vector<TestLineInfo> testLines {
        {"env", "ani_env *"},
        {"ref0", "ani_ref", "wrong reference"},
        {"ref1", "ani_ref"},
        {"result", "ani_boolean *"},
    };
    ASSERT_ERROR_ANI_ARGS_MSG("Reference_Equals", testLines);
}

TEST_F(ReferenceEqualsTest, wrong_ref1)
{
    ani_boolean res = ANI_FALSE;
    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullRef, nullptr, &res), ANI_ERROR);
    std::vector<TestLineInfo> testLines {
        {"env", "ani_env *"},
        {"ref0", "ani_ref"},
        {"ref1", "ani_ref", "wrong reference"},
        {"result", "ani_boolean *"},
    };
    ASSERT_ERROR_ANI_ARGS_MSG("Reference_Equals", testLines);
}

TEST_F(ReferenceEqualsTest, wrong_res)
{
    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullRef, nullRef, nullptr), ANI_ERROR);
    std::vector<TestLineInfo> testLines {
        {"env", "ani_env *"},
        {"ref0", "ani_ref"},
        {"ref1", "ani_ref"},
        {"result", "ani_boolean *", "wrong pointer for storing 'ani_boolean'"},
    };
    ASSERT_ERROR_ANI_ARGS_MSG("Reference_Equals", testLines);
}

TEST_F(ReferenceEqualsTest, wrong_all_args)
{
    ASSERT_EQ(env_->c_api->Reference_Equals(nullptr, nullptr, nullptr, nullptr), ANI_ERROR);
    std::vector<TestLineInfo> testLines {
        {"env", "ani_env *", "called from incorrect the native scope"},
        {"ref0", "ani_ref", "wrong reference"},
        {"ref1", "ani_ref", "wrong reference"},
        {"result", "ani_boolean *", "wrong pointer for storing 'ani_boolean'"},
    };
    ASSERT_ERROR_ANI_ARGS_MSG("Reference_Equals", testLines);
}

TEST_F(ReferenceEqualsTest, throw_error)
{
    ThrowError();
    ani_boolean res {};
    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullRef, nullRef, &res), ANI_ERROR);
    std::vector<TestLineInfo> testLines {
        {"env", "ani_env *", "has unhandled an error"},
        {"ref0", "ani_ref"},
        {"ref1", "ani_ref"},
        {"result", "ani_boolean *"},
    };
    ASSERT_ERROR_ANI_ARGS_MSG("Reference_Equals", testLines);

    ASSERT_EQ(env_->ResetError(), ANI_OK);
}

TEST_F(ReferenceEqualsTest, global_ref)
{
    ani_ref gref1 {};
    ani_ref gref2 {};
    ASSERT_EQ(env_->GlobalReference_Create(nullRef, &gref1), ANI_OK);
    ASSERT_EQ(env_->GlobalReference_Create(nullRef, &gref2), ANI_OK);
    ani_boolean res {};
    ASSERT_EQ(env_->c_api->Reference_Equals(env_, gref1, gref2, &res), ANI_OK);
    ASSERT_EQ(res, ANI_TRUE);

    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullRef, gref2, &res), ANI_OK);
    ASSERT_EQ(res, ANI_TRUE);

    ASSERT_EQ(env_->GlobalReference_Delete(gref2), ANI_OK);
    ASSERT_EQ(env_->GlobalReference_Delete(gref1), ANI_OK);
}

// NOTE: enable when #28700 is resolved
TEST_F(ReferenceEqualsTest, DISABLED_stack_ref_local)
{
    ani_function fn {};
    ASSERT_EQ(env_->Module_FindFunction(module, "checkStackRefWithLocal", ":", &fn), ANI_OK);
    ASSERT_EQ(env_->Function_Call_Void(fn), ANI_OK);
}

// NOTE: enable when #28700 is resolved
TEST_F(ReferenceEqualsTest, DISABLED_stack_ref_global)
{
    ani_function fn {};
    ASSERT_EQ(env_->Module_FindFunction(module, "checkStackRefWithGlobal", ":", &fn), ANI_OK);
    ASSERT_EQ(env_->Function_Call_Void(fn), ANI_OK);
}

TEST_F(ReferenceEqualsTest, success)
{
    ani_boolean res = ANI_FALSE;

    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullRef, nullRef, &res), ANI_OK);
    ASSERT_EQ(res, ANI_TRUE);

    ASSERT_EQ(env_->c_api->Reference_Equals(env_, nullRef, static_cast<ani_ref>(str), &res), ANI_OK);
    ASSERT_EQ(res, ANI_FALSE);
}

}  // namespace ark::ets::ani::verify::testing
